/******************************************************************************

	COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2009, 2010.

	This file is part of Maxsi Engine.

	Maxsi Engine is free software: you can redistribute it and/or modify it
	under the terms of the GNU Lesser General Public License as published by
	the Free Software Foundation, either version 3 of the License, or (at your
	option) any later version.

	Maxsi engine is distributed in the hope that it will be useful, but WITHOUT
	ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
	FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
	for more details.

	You should have received a copy of the GNU Lesser General Public License
	along with Maxsi Engine. If not, see <http://www.gnu.org/licenses/>.

	MaxsiEngine.dll
	A general purpose C++ library for the Maxsi Technology Project.

	MaxsiIOFileSystem.h
	A Simple Flexible Async I/O FileSystem.

******************************************************************************/

#include "MaxsiEngine.h"

#if 0

#if !defined(ME_IODisabled) || defined(ME_IOEnabled)

BeginMaxsiNamespace

ME_IOFileSystem::ME_IOFileSystem()
{
	IFileName		=	NULL;
	OFileName		=	NULL;
	IFileHND		=	INVALID_HANDLE_VALUE;
	OFileHND		=	INVALID_HANDLE_VALUE;
	IFileWriting	=	false;
	IFileWriting	=	false;
	memset(&IOverlapped,0,sizeof(IOverlapped));
	memset(&OOverlapped,0,sizeof(OOverlapped));
}

ME_IOFileSystem::~ME_IOFileSystem()
{
	if ( IFileName ) { delete[] IFileName; }
	if ( OFileName ) { delete[] OFileName; }
	ME_DONT_COMPILE_RELEASE_BUILD();
	//IFileHND		// Todo: If open, close this!
	//OFileHND		// Todo: If open, close this!
}

SIZE_T ME_IOFileSystem::IBufferRequired()
{
	// Actually, not a lot of space is required for these operations.
	return 16384; // 16 KiB
}

SIZE_T ME_IOFileSystem::OBufferRequired()
{
	// Actually, not a lot of space is required for these operations.
	return 16384; // 16 KiB
}

SIZE_T ME_IOFileSystem::IBufferSuggested()
{
	// For streaming purposes, let's have a good amount of memory available so we won't be a bottleneck.
	return 1024*1024; // 1 MiB
}

SIZE_T ME_IOFileSystem::OBufferSuggested()
{
	// For streaming purposes, let's have a good amount of memory available so we won't be a bottleneck.
	return 1024*1024; // 1 MiB
}

int ME_IOFileSystem::IPolicy()
{
	return ME_IO_CTX_PERSONALBUFFER;
}

int ME_IOFileSystem::OPolicy()
{
	return ME_IO_CTX_PERSONALBUFFER;
}

int ME_IOFileSystem::ORangePolicy()
{
	return ME_IO_RANGES_SUPPORTED;
}

int ME_IOFileSystem::IRangePolicy()
{
	return ME_IO_RANGES_SUPPORTED;
}

SIZE_T ME_IOFileSystem::GetIBytesAvailable()
{
	SIZE_T	Result;

	IContext->Buffer->Lock( ME_BUFFER_LOCK_PARAMS );

	Result	=	IContext->Buffer->GetBufferAvailable();

	IContext->Buffer->Unlock();

	return Result;
}

SIZE_T ME_IOFileSystem::GetOBytesAvailable()
{
	SIZE_T	Result;

	OContext->Buffer->Lock( ME_BUFFER_LOCK_PARAMS );

	Result	=	OContext->Buffer->GetBufferAvailable();

	OContext->Buffer->Unlock();

	return Result;
}

bool ME_IOFileSystem::OnThink()
{
	bool IThinkResult = OnIThink();
	bool OThinkResult = OnOThink();

	return (IThinkResult && OThinkResult);
}

bool ME_IOFileSystem::OnIThink()
{
	if ( !II ) { return false; }
	if ( !IContext ) { return false; }
	if ( !IContext->Buffer ) { return false; }

	if ( IFileWriting ) { return false; }

	ME_Buffer*	Buffer	=	IContext->Buffer;

	Buffer->Lock( ME_BUFFER_LOCK_PARAMS );

	if ( !Buffer->IsInUse() && Buffer->MayRead() == ME_ERROR_SUCCESS && Buffer->MayWrite() == ME_ERROR_SUCCESS )
	{
		if ( Buffer->GetBufferUsed() )
		{
			ME_IORange*	Range		=	GetIRangePos(II.Total);

			if ( Range == NULL || Range->FileName == NULL )
			{
				// Todo: Handle this gracefully.
				IOAssertionFailure(II, 0, "ME_IOFileSystem::OnIThink() requires properly ranged input!");
			}
			else // Range != NULL && Range->FileName != NULL
			{
				if ( OpenWrite(Range->FileName) )
				{						
					//Bytes_t		RangeSize		=	Range->End-Range->Start+1;
					Bytes_t		RangeLeft		=	Range->End-II.Total;
					Bytes_t		RangeOffset		=	Range->Offset+(II.Total-Range->Start);
					char*		Pointer			=	Buffer->GetPointer();
					SIZE_T		WriteLength;

					// Do we have the rest of the range in out buffer?
					if ( RangeLeft > Buffer->GetBufferUsed() )
					{
						// No. There is more. Just write what we do have.
						WriteLength		=	Buffer->GetBufferUsed();
					}
					else
					{
						// Oh yes! Just write it all!
						WriteLength		=	(SIZE_T)RangeLeft;
					}

					// Do we have the entire buffer in one row?			
					if ( Buffer->GetPointerSizeFollowingData() < WriteLength )
					{
						// Nope. Just limit our length.
						WriteLength		=	Buffer->GetPointerSizeFollowingData();
					}

					// Make sure we support files over 4 GB properly!
					IOverlapped.Offset		=	(DWORD)(RangeOffset&0xFFFFFFFF);
					IOverlapped.OffsetHigh	=	(DWORD)(RangeOffset>>32);

					Buffer->SetInUse();

					ScheduleNextThink(ME_NEXT_THINK_WINCALLBACK);

					// Write the file. Our think function will be called when we are done.
					if ( WriteFile((HANDLE)IFileHND,
						Pointer,
						(DWORD)WriteLength,
						NULL,
						&IOverlapped) == FALSE )
					{
						DWORD nLastError = GetLastError();
						if (ERROR_IO_PENDING != nLastError)
						{
							Buffer->ClearInUse();

							// An error happened. Handle the error.
							char*		Error	=	GetWinErrorMessage(GetLastError());
							IOUnavailable(II, GetLastError(), Error);
							delete[]	Error;
						}
					}
				}
				else
				{
					// We failed. Any errors were caught in OpenWrite().
				}
			}
		}
		else
		{
			// No data has arrived yet, just wait.
			ScheduleNextThink(ME_IO_THINK_NO_DATA_YET);
		}
	}
	else
	{
		// We are currenly waiting for a buffer read/write operation to complete.
		ScheduleNextThink(ME_IO_THINK_WINCALLBACK);			
	}

	Buffer->Unlock();

	return true;
}

bool ME_IOFileSystem::OpenWrite(char* File)
{
	if ( !File )
	{
		IOAssertionFailure(II, 0, "ME_IOFileSystem::OpenWrite() had File == NULL!");				
		return false;
	}

	// Don't open the same file twice!
	if ( !File || !IFileName || CompareFilePaths(File,IFileName) != 0 )
	{
		if ( IFileHND != INVALID_HANDLE_VALUE ) { CloseHandle(IFileHND); IFileHND = INVALID_HANDLE_VALUE; }

		// Make sure the path exists!
		CreateFolderRecursive(File);

		// TODO: Does CreateFile block, and if it does, does it block enough to make it worthfile to open acsync,
		// and is it even possible to open async in the first place, like it is with network stream connections?
		if (INVALID_HANDLE_VALUE == (IFileHND = CreateFileA(File,GENERIC_WRITE,FILE_SHARE_READ | FILE_SHARE_WRITE,NULL,OPEN_ALWAYS,FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_OVERLAPPED,NULL) ))
		{
			char*		Error	=	GetWinErrorMessage(GetLastError());
			IOUnavailable(II, GetLastError(), Error);
			delete[]	Error;

			return false;
		}

		// Clear the possible ERROR_ALREADY_EXISTS
		SetLastError(0);

		if (0 == BindIoCompletionCallback(IFileHND, Maxsi::ME_IOConnection__DoneIO, 0) )
		{
			IOAssertionFailure(II, 0, "ME_IOFileSystem::OpenWrite() couldn't BindIoCompletionCallback()!");
			return false;
		}

		if ( IFileName ) { delete[] IFileName; }
		IFileName		=	BuildString(1,File);
	}
	return true;
}

bool	ME_IOFileSystem::IIConnected()
{
	return BaseClass::IIConnected();
}
bool	ME_IOFileSystem::IOConnected()
{
	return BaseClass::IOConnected();
}
bool	ME_IOFileSystem::OIConnected()
{
	return BaseClass::OIConnected();
}
bool	ME_IOFileSystem::OOConnected()
{
	return BaseClass::OOConnected();
}
bool	ME_IOFileSystem::IIDisconnect()
{
	return BaseClass::IIDisconnect();
}
bool	ME_IOFileSystem::OODisconnect()
{
	return BaseClass::OODisconnect();
}
bool	ME_IOFileSystem::IIDisconnected()
{
	return BaseClass::IIDisconnected();
}
bool	ME_IOFileSystem::IODisconnected()
{
	return BaseClass::IODisconnected();
}
bool	ME_IOFileSystem::OIDisconnected()
{
	return BaseClass::OIDisconnected();
}
bool	ME_IOFileSystem::OODisconnected()
{
	return BaseClass::OODisconnected();
}	
bool	ME_IOFileSystem::IIConnect(ME_IOConnectionPointer* Remote)
{
	return BaseClass::IIConnect(Remote);
}
bool	ME_IOFileSystem::OOConnect(ME_IOConnectionPointer* Remote)
{
	return BaseClass::IIConnect(Remote);
}
bool	ME_IOFileSystem::Connected(ME_IOConnectionPointer* Connection)
{
	return BaseClass::IIConnect(Connection);
}
bool	ME_IOFileSystem::Disconnect(ME_IOConnectionPointer* Connection)
{
	return BaseClass::Disconnect(Connection);
}
bool	ME_IOFileSystem::Disconnect()
{
	return BaseClass::Disconnect();
}
bool	ME_IOFileSystem::Disconnected(ME_IOConnectionPointer* Connection)
{
	return BaseClass::Disconnected(Connection);
}
bool	ME_IOFileSystem::Disconnected()
{
	return BaseClass::Disconnected();
}
bool	ME_IOFileSystem::EvaluateConnection()
{
	ME_TODO("Implement this!");
	return true;
}

bool	ME_IOFileSystem::OnOThink()
{
	return true;
}

void	ME_IOFileSystem::DoneIO(DWORD dwErrorCode, DWORD dwNumberOfBytesTransferred, LPOVERLAPPED lpOverlapped)
{
	BaseClass::DoneIO(dwErrorCode,dwNumberOfBytesTransferred,lpOverlapped);
}

EndMaxsiNamespace

#endif

#endif

